/**
 * ORIPA - Origami Pattern Editor 
 * Copyright (C) 2005-2009 Jun Mitani http://mitani.cs.tsukuba.ac.jp/

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

package oripa;

import java.awt.*;
import java.awt.event.*;
import java.awt.geom.AffineTransform;
import java.awt.geom.Line2D;
import java.awt.geom.Point2D;
import javax.swing.JPanel;
import javax.vecmath.Vector2d;
import oripa.geom.OriFace;
import oripa.geom.OriHalfedge;
import oripa.geom.OriLine;

public class ModelViewScreen extends JPanel
        implements MouseListener, MouseMotionListener, MouseWheelListener, ActionListener, ComponentListener {

    private Image bufferImage;
    private Graphics2D bufferg;
    private Point2D preMousePoint; // Screen coordinates
    private Point2D.Double currentMousePointLogic = new Point2D.Double(); // Logical coordinates
    private double scale;
    private double transX;
    private double transY;
    private Vector2d modelCenter = new Vector2d();
    private Dimension preSize;
    private double rotateAngle;
    private AffineTransform affineTransform = new AffineTransform();
    public boolean dispSlideFace = false;
    private OriLine crossLine = null;
    private int crossLineAngleDegree = 90;
    private double crossLinePosition = 0;

    public ModelViewScreen() {
        addMouseListener(this);
        addMouseMotionListener(this);
        addMouseWheelListener(this);
        addComponentListener(this);

        crossLine = new OriLine();
        scale = 1.0;
        rotateAngle = 0;
        setBackground(Color.white);

        preSize = getSize();
    }

    public void resetViewMatrix() {
        rotateAngle = 0;
        if (!ORIPA.doc.hasModel) {
            scale = 1.0;
        } else {
            // Align the center of the model, combined scale
            Vector2d maxV = new Vector2d(-Double.MAX_VALUE, -Double.MAX_VALUE);
            Vector2d minV = new Vector2d(Double.MAX_VALUE, Double.MAX_VALUE);
            for (OriFace face : ORIPA.doc.faces) {
                for (OriHalfedge he : face.halfedges) {
                    maxV.x = Math.max(maxV.x, he.vertex.p.x);
                    maxV.y = Math.max(maxV.y, he.vertex.p.y);
                    minV.x = Math.min(minV.x, he.vertex.p.x);
                    minV.y = Math.min(minV.y, he.vertex.p.y);
                }
            }
            modelCenter.x = (maxV.x + minV.x) / 2;
            modelCenter.y = (maxV.y + minV.y) / 2;
            scale = 0.8 * Math.min(getWidth() / (maxV.x - minV.x), getHeight() / (maxV.y - minV.y));
            updateAffineTransform();
            recalcCrossLine();
        }
    }

    public void drawModel(Graphics2D g2d) {
        for (OriFace face : ORIPA.doc.sortedFaces) {
            if (Globals.modelDispMode == Constants.ModelDispMode.FILL_COLOR) {
                if (face.faceFront) {
                    g2d.setColor(new Color(255, 200, 200));
                } else {
                    g2d.setColor(new Color(200, 200, 255));
                }
                g2d.fill(face.outline);
            } else if (Globals.modelDispMode == Constants.ModelDispMode.FILL_WHITE) {
                g2d.setColor(Color.WHITE);
                g2d.fill(face.outline);
            } else if (Globals.modelDispMode == Constants.ModelDispMode.FILL_ALPHA) {
                g2d.setColor(new Color(100, 100, 100));
                g2d.fill(face.outline);
            }

            g2d.setColor(Color.BLACK);
            for (OriHalfedge he : face.halfedges) {
                if (he.pair == null) {
                    g2d.setStroke(Config.MODEL_STROKE_CUT);
                } else {
                    g2d.setStroke(Config.STROKE_CUT);
                }
                g2d.draw(new Line2D.Double(he.positionForDisplay.x, 
                        he.positionForDisplay.y, he.next.positionForDisplay.x, 
                        he.next.positionForDisplay.y));
            }
        }

        if (Globals.bDispCrossLine) {
            g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
            g2d.setStroke(Config.MODEL_STROKE_CUT);
            g2d.setColor(Color.RED);

            g2d.draw(new Line2D.Double(crossLine.p0.x, crossLine.p0.y, crossLine.p1.x, crossLine.p1.y));
        }
    }

    // Update the current AffineTransform
    private void updateAffineTransform() {
        affineTransform.setToIdentity(); 
        affineTransform.translate(getWidth() * 0.5, getHeight() * 0.5);
        affineTransform.scale(scale, scale);   
        affineTransform.translate(transX, transY);
        affineTransform.rotate(rotateAngle);
        affineTransform.translate(-modelCenter.x, -modelCenter.y);
    }

    // Scaling relative to the center of the screen
    @Override
    public void paintComponent(Graphics g) {
        super.paintComponent(g);

        if (bufferImage == null) {
            bufferImage = createImage(getWidth(), getHeight());
            bufferg = (Graphics2D) bufferImage.getGraphics();

            updateAffineTransform();
            preSize = getSize();
        }
        bufferg.setRenderingHint(RenderingHints.KEY_ANTIALIASING,
                RenderingHints.VALUE_ANTIALIAS_ON);
        bufferg.setTransform(new AffineTransform());
        bufferg.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 1.0f));
        bufferg.setColor(Color.WHITE);
        bufferg.fillRect(0, 0, getWidth(), getHeight());

        bufferg.setTransform(affineTransform);

        Graphics2D g2d = bufferg;


        if (ORIPA.doc.hasModel) {
            g2d.setStroke(Config.STROKE_CUT);
            if (Globals.modelDispMode == Constants.ModelDispMode.FILL_ALPHA) {
                g2d.setComposite(AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.1f));
            }
            drawModel(g2d);
            g.drawImage(bufferImage, 0, 0, this);
        }
    }

    @Override
    public void mouseClicked(MouseEvent e) {
        if (Globals.editMode == Constants.EditMode.NONE) {
            return;
        }
        Point2D.Double clickPoint = new Point2D.Double();
        try {
            affineTransform.inverseTransform(e.getPoint(), clickPoint);
        } catch (Exception ex) {
        }
    }

    public void setCrossLineAngle(int angleDegree) {
        crossLineAngleDegree = angleDegree;
        recalcCrossLine();
    }

    public void setCrossLinePosition(int positionValue) {
        crossLinePosition = positionValue;
        recalcCrossLine();
    }

    public void recalcCrossLine() {
        Vector2d dir = new Vector2d(Math.cos(Math.PI * crossLineAngleDegree / 180.0), 
                Math.sin(Math.PI * crossLineAngleDegree / 180.0));
        crossLine.p0.set(modelCenter.x - dir.x * 300, modelCenter.y - dir.y * 300);
        crossLine.p1.set(modelCenter.x + dir.x * 300, modelCenter.y + dir.y * 300);
        Vector2d moveVec = new Vector2d(-dir.y, dir.x);
        moveVec.normalize();
        moveVec.scale(crossLinePosition);
        crossLine.p0.add(moveVec);
        crossLine.p1.add(moveVec);

        ORIPA.doc.setCrossLine(crossLine);
        repaint();
        ORIPA.mainFrame.mainScreen.repaint();
    }

    @Override
    public void mousePressed(MouseEvent e) {
        preMousePoint = e.getPoint();
    }

    @Override
    public void mouseReleased(MouseEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseEntered(MouseEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseExited(MouseEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void mouseDragged(MouseEvent e) {
        if (javax.swing.SwingUtilities.isRightMouseButton(e)) {
            transX += (double) (e.getX() - preMousePoint.getX()) / scale;
            transY += (double) (e.getY() - preMousePoint.getY()) / scale;

            preMousePoint = e.getPoint();
            updateAffineTransform();
            repaint();
        } else if (javax.swing.SwingUtilities.isLeftMouseButton(e)) {
            rotateAngle += ((double) e.getX() - preMousePoint.getX()) / 100.0;
            preMousePoint = e.getPoint();
            updateAffineTransform();
            repaint();
        }

        // Gets the value of the current logical coordinates of the mouse
        try {
            affineTransform.inverseTransform(e.getPoint(), currentMousePointLogic);
        } catch (Exception ex) {
        }
    }

    @Override
    public void mouseMoved(MouseEvent e) {
        try {
            affineTransform.inverseTransform(e.getPoint(), currentMousePointLogic);
        } catch (Exception ex) {
        }
    }

    @Override
    public void mouseWheelMoved(MouseWheelEvent e) {
        double scale_ = (100.0 - e.getWheelRotation() * 5) / 100.0;
        scale *= scale_;
        updateAffineTransform();
        repaint();
    }

    @Override
    public void actionPerformed(ActionEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void componentResized(ComponentEvent arg0) {
        preSize = getSize();

        transX = transX - preSize.width * 0.5 + getWidth() * 0.5;
        transY = transY - preSize.height * 0.5 + getHeight() * 0.5;

        bufferImage = createImage(getWidth(), getHeight());
        bufferg = (Graphics2D) bufferImage.getGraphics();

        updateAffineTransform();
        repaint();
    }

    @Override
    public void componentMoved(ComponentEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void componentShown(ComponentEvent arg0) {
        // TODO Auto-generated method stub
    }

    @Override
    public void componentHidden(ComponentEvent arg0) {
        // TODO Auto-generated method stub
    }
}
